//
// Created by martin on 8/3/21.
//

#include <string.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <stddef.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <strex.h>

#include "error.h"
#include "dir.h"

/**
 * 新建dir对象
 * @param root_phy 根目录物理路径. 必须是以"/"开头的绝对路径，结尾可以含"/"，也可以不包含
 * @return
 * - NULL 创建对象失败
 * - Not-NULL 创建对象成功
 */
Dir* dir_new(const char *root_phy)
{
    if (!root_phy) {
        err_msg("root_phy directory can't be null");
        return NULL;
    }

    /* Dir 对象 */
    Dir *dir = malloc(sizeof(Dir));
    if (!dir) {
        err_ret("malloc Dir failed");
        return NULL;
    }

    /* 根据root_phy字符串长度，申请rd_phy空间 */
    size_t len = strlen(root_phy);
    if (len > 1) {
        // root_phy非以"/"结尾，额外申请一个空间，用于添加末尾"/"
        if (!str_end_with(root_phy, '/'))
            len++;
    }
    dir->rd_phy = malloc( len + 1); /* +1, 为存储null byte */
    if (!dir->rd_phy) {
        err_ret("malloc rd_phy failed");
        return NULL;
    }

    // rd_phy = root_phy + "/"(如果root_phy末尾未包含)
    strcpy(dir->rd_phy, root_phy);
    dir->rd_phy[len - 1] = '/'; /* 确保 rd_phy 以"/"结尾 */
    dir->rd_phy[len] = 0; /* null byte */

    /* 申请cwd空间 */
    long path_max = pathconf(dir->rd_phy, _PC_PATH_MAX);
    if (path_max == -1) {
        path_max = PATH_MAX_GUESS; /* guess */
    }
    dir->cwd_vir = malloc(path_max);
    if (!dir->cwd_vir) {
        err_ret("malloc cwd_vir failed");
        return NULL;
    }

    // cwd = "/"
    strcpy(dir->cwd_vir, "/");
    return dir;
}

/**
 * 释放dir对象空间
 * @param dir 包含root物理路径和cwd（当前工作目录）路径信息
 */
void dir_free(Dir *dir)
{
    if (!dir) return;
    if (dir->cwd_vir) {
        free(dir->cwd_vir);
        dir->cwd_vir = NULL;
    }
    if (dir->rd_phy) {
        free(dir->rd_phy);
        dir->rd_phy = NULL;
    }
}

/**
 * 打印dir成员信息
 * @param dir 包含root物理路径和cwd（当前工作目录）路径信息
 */
void dir_print(Dir *dir)
{
    if (!dir) return;
    printf("root phy: %s; cwd: %s\n", dir->rd_phy, dir->cwd_vir);
}

long dir_get_path_max(Dir *dir)
{
    long path_max = pathconf(dir->rd_phy, _PC_PATH_MAX);
    if (path_max == -1) {
        path_max = PATH_MAX_GUESS; /* guess */
    }

    return path_max;
}


/**
 * 判断指定文件是否为目录
 * @param dir Dir对象，包含用户工作目录信息
 * @param file_path 文件名或文件路径
 * @return
 * - 1 文件存在且为目录
 * - 0 访问文件出错或不是目录
 */
int dir_isDir(Dir *dir, const char *file_path)
{
    if (!dir || !file_path) return 0;

    char tmp_path[1024];
    if (dir_getPhysicalPath(dir, file_path, tmp_path, sizeof(tmp_path)) < 0) {
        err_msg("get file(%s) physical tmp_path failed on", file_path);
        return 0;
    }

    struct stat st;
    if (stat(tmp_path, &st) < 0) { /* 访问文件状态出错 */
        if (errno == ENOENT) { // 文件或目录不存在
            return 0;
        }
        err_ret("dir_isDir stat error on tmp_path %s", tmp_path);
        return 0;
    }

    if (S_ISDIR(st.st_mode)) { /* 目录 */
        return 1;
    }

    return 0;
}

/**
 * 判断指定文件是否存在
 * @param dir Dir对象，包含了当前FTP工作区间路径信息
 * @param file_path 文件路径，可能是基于当前FTP工作区间的文件名，也可能是基于FTP根路径的绝对路径
 * @return 文件存在状态
 * -0 文件不存在
 * -1 文件存在
 */
int dir_exist(Dir *dir, const char *file_path)
{
    if (!dir || !file_path) return 0;

    char path[1024];
    if (dir_getPhysicalPath(dir, file_path, path, sizeof(path)) < 0) {
        err_msg("get file(%s) physical path failed on", file_path);
        return 0;
    }

    struct stat st;
    if (stat(path, &st) < 0) {
        if (errno == ENOENT) { // 文件或目录不存在
            return 0;
        }
        else err_msg("stat file error on %s", path);
    }

    return 1;
}

/**
 * 判断路径dir_path是否为root目录
 * @param dir cwd信息
 * @param dir_path 要检查的目录路径
 * @return
 * @note "/" 是根目录，其他均为非根目录
 */
int dir_isRootDir(Dir *dir, const char *dir_path)
{
    if (!dir || !dir_path) return 0;

    return (strcmp(dir->cwd_vir, "/") == 0);
}

/**
 * 重命名文件
 * @param dir cwd信息
 * @param old_path 旧的文件名或文件路径
 * @param new_path 新的文件名或文件路径
 * @return 重命名结果
 * - -1 失败
 * - 0 成功，旧的文件移动至新文件路径处
 */
int dir_rename(Dir *dir, const char *old_path, const char *new_path)
{
    if (!dir || !old_path || !new_path) return -1;
    char old_path_phy[PATH_MAX];
    char new_path_phy[PATH_MAX];

    // 获得旧的文件物理路径
    if (dir_getPhysicalPath(dir, old_path, old_path_phy, sizeof(old_path_phy)) < 0) {
        err_msg("dir_getPhysicalPath failed on path %s", old_path);
        return -1;
    }

    // 获得新的文件物理路径
    if (dir_getPhysicalPath(dir, new_path, new_path_phy, sizeof(new_path_phy)) < 0) {
        err_msg("dir_getPhysicalPath failed on path %s", old_path);
        return -1;
    }

    // 重命名文件
    if (rename(old_path_phy, new_path_phy) < 0) {
        err_ret("rename %s to %s failed", old_path_phy, new_path_phy);
        return -1;
    }

    return 0;
}

/**
 * 获取cwd的父目录
 * @param dir cwd信息
 * @param buf 存储cwd父目录的缓存区，由调用者提供
 * @param size buf大小
 * @return
 * - -1 失败
 * - 0 成功，父目录拷贝至buf
 * @note 当前工作目录 cwd = current working directory = cwd_vir
 * 例如，cwd: /, /home, /home/martin, /home/martin/
 * "/" = root dir = root_vir (非 root_phy)
 */
int dir_getCurParentDir(Dir *dir, char *buf, size_t size)
{
    if (!dir || !dir->cwd_vir || !buf || size < 1) return -1;

    // 检查cwd是否为root dir
    if (strcmp(dir->cwd_vir, "/") == 0) {
        strncpy(buf, dir->cwd_vir, size);
        return 0;
    }

    // 检查cwd是否以 / 开头
    if (!str_start_with(dir->cwd_vir, '/')) {
        err_msg("cwd(%s) do not start with '/'", dir->cwd_vir);
        return -1;
    }

    // 找到cwd的父目录对应 "/" 的位置
    int len = (int)strlen(dir->cwd_vir);
    int i; /* cwd的父路径对应 "/" 的位置 */
    // 找到父目录结尾"/", 忽略末尾字符(即使是 "/")
    for (i = len - 2; i >= 0; i--) {
        if (dir->cwd_vir[i] == '/') break;
    }

    // 父目录是root dir
    if (i == 0) {
        // buf = "/"
        strncpy(buf, "/", size);
        return 0;
    }

    // 父目录不是root dir
    // 复制cwd[0..i] 至 buf[0..i], buf[i] == "/"
    int j;
    for (j = 0; j <= i && j < (int)size - 2; ++j) {
        buf[j] = dir->cwd_vir[j];
    }
    buf[j] = 0; /* null byte */

    return 0;
}

/**
 * get file size(bytes)
 * @param dir cwd info
 * @param file_path target file's path
 * @return
 * @note To get file size need to get the physical path of target file on the disk
 */
ssize_t dir_getFileSize(Dir *dir, const char *file_path)
{
    if (!dir || !file_path) return 0;

    char path[PATH_MAX];
    if (dir_getPhysicalPath(dir, file_path, path, sizeof(path)) < 0) {
        err_msg("get file(%s) physical path failed on", file_path);
        return -1;
    }

    struct stat st;
    if (stat(path, &st) < 0) {
        err_ret("stat error on file %s", path);
        return -1;
    }

    return st.st_size;
}

/**
 * 获取目标文件路径file_path对应的物理路径
 * @param dir 包含root物理路径信息和cwd信息
 * @param file_path 目标文件路径。如果以"/"开头，表明是绝对路径；否则，表面是相对路径。
 * @param[out] buf 存储目标文件的物理路径
 * @param size buf大小，必须足够大以存储结果
 * @return
 * - -1 失败
 * - 0 成功，buf填充目标文件的物理路径
 * @details
 * 如果file_path是相对路径，有
 * physical path = root_phy + cwd + file_path
 *
 * 如果file_path是绝对路径，有
 * physical path = root_phy + file_path
 * @note 函数只是获取file_path对应路径，不要求文件必须真实存在
 */
int dir_getPhysicalPath(Dir *dir, const char *file_path, char *buf, size_t size)
{
    if (!dir || !dir->rd_phy || !file_path || !buf || size <= 0) return -1;
    if (!str_start_with(dir->rd_phy, '/')) {
        err_msg("dir_getPhysicalPath root directory not start with '/'");
        return -1;
    }

    // 检查file_path 是否以'/'开头
    if (str_start_with(file_path, '/')) {
        // 绝对路径
        // rd_phy + file_path
        snprintf(buf, size, "%s%s", dir->rd_phy, file_path + 1);
    }
    else {
        // 相对路径
        // rd_phy  + cwd_vir + file_path
        snprintf(buf, size, "%s%s%s", dir->rd_phy, dir->cwd_vir + 1, file_path);
    }

    return 0;
}

/**
 * 检查指定文件是否为普通文件
 * @param file_path 目标文件路径，需要是绝对路径
 * @return 判断结果
 * - 1 是普通文件
 * - 0 不是普通文件
 */
int dir_isRegFile(const char *file_path)
{
    struct stat st;
    if (stat(file_path, &st) < 0) { /* error */
        err_ret("dir_isRegFile stat error on path %s", file_path);
        return 0;
    }

    if (S_ISREG(st.st_mode)) { /* regular file */
        return 1;
    }
    return 0;
}

/**
 * 改变cwd至目标路径
 * @param dir 包含cwd信息
 * @param path 目标目录路径. 如果以'/'开头，表明是绝对路径； 如果不是，说明是相对路径
 * @return
 * - -1 failure
 * - 0 success
 * @note
 * "." 当前目录;
 * ".." 父目录;
 * ”/“ root路径
 */
int dir_chdir(Dir *dir, const char *path)
{
    if (!dir || !dir->cwd_vir || !path || strlen(path) < 1) return -1;
    char tmp[PATH_MAX];

    // 检查目标路径是否为当前目录
    if (strcmp(path, ".") == 0) {
        return 0; /* do nothing */
    }
    // 检查目标路径是否为父目录
    if (strcmp(path, "..") == 0) {
        // 检查cwd是否为root
        if (strcmp(dir->cwd_vir, "/") == 0) {
            // cwd == "/", 没有父目录
            return 0;
        }

        // cwd 不是root，表明存在父目录
        if (dir_getCurParentDir(dir, tmp, sizeof(tmp)) < 0) {  /* error */
            err_msg("dir_getCurParentDir error");
            return -1;
        }
        strncpy(dir->cwd_vir, tmp, dir_get_path_max(dir));
        return 0;
    }

    // neither "." nor ".."
    // check if path is absolute path
    if (str_start_with(path, '/')) {
        // check if path is a real directory path
        if (!dir_isDir(dir, path)) { /* path is not a dir */
            err_msg("%s is not a valid dir.", path);
            return -1;
        }

        // path is a dir
        // cwd = path
        char *s = path;
        int i = 0;
        while (*s != '\0') {
            dir->cwd_vir[i++] = *s;
            s++;
        }
        if (dir->cwd_vir[i - 1] != '/') {
            dir->cwd_vir[i++] = '/';
        }
        dir->cwd_vir[i] = 0;
        return 0;
    }

    // relative path
    // check if path is a real directory path
    if (!dir_isDir(dir, path)) { /* path is not a dir */
        err_msg("%s is not a valid dir.", path);
        return -1;
    }

    // cwd = cwd + path
    if (!str_end_with(dir->cwd_vir, '/')) {
        strcat(dir->cwd_vir, "/");
    }
    strcat(dir->cwd_vir, path);
    return 0;
}

/**
 * get cwd path
 * @param dir cwd info
 * @param[out] buf to store cwd path
 * @param size byte number of buf, need to be large enough to store result
 * @return
 */
char *dir_getCWD(Dir *dir, char *buf, size_t size)
{
    if (!dir || !buf || size < 1) return NULL;
    strncpy(buf, dir->cwd_vir, size);
}

/**
 * get physical path of cwd
 * @param dir cwd info
 * @param[out] buf to store cwd physical path
 * @param size byte number of buf, need to be large enough to store result
 * @return
 * - NULL failure
 * - Not-NULL sucess, buf store the physical path of cwd
 */
char *dir_getCWDPhysicalPath(Dir *dir, char *buf, size_t size)
{
    if (!dir || !dir->cwd_vir || !buf || size < 1) return NULL;

    if (dir_getPhysicalPath(dir, dir->cwd_vir, buf, size - 1) < 0) {
        return NULL;
    }

    if (!str_end_with(dir->cwd_vir, '/')) {
        strcat(dir->cwd_vir, "/");
    }
    return buf;
}

/**
 * get 10bits permission info of target path file_path
 * @param file_path target file path
 * @param[out] buf to 10bits permission info
 * @param size byte number of buf, need to be large enough to store result
 * @return
 * @details 10bits permission just like form "-rwxrwxrwx", "drwxrwxr--", etc.
 */
int get_permission_10bit(char *file_path, char *buf, size_t size)
{
    char perm[] = "----------"; /* "-rwxrwxrwx" */
    struct stat st;

    if (buf == NULL || size < sizeof(perm)) return -1;

    if (stat(file_path, &st) < 0) {
        perror("get_permission_10bit stat error");
        return -1;
    }

    /* file type */
    switch (st.st_mode & S_IFMT) {
        case S_IFREG: /* regular file */
            perm[0] = '-';
            break;
        case S_IFDIR:/* directory */
            perm[0] = 'd';
            break;
        case S_IFLNK: /* symbol link file(soft link) */
            perm[0] = 'l';
            break;
        case S_IFCHR: /* char device */
            perm[0] = 'c';
            break;
        case S_IFBLK: /* block device */
            perm[0] = 'b';
            break;
        default: /* unknown type, used as regular file */
            perm[0] = '-';
    }

    /* owner's permission */
    if (st.st_mode & S_IRUSR)
        perm[1] = 'r';
    if (st.st_mode & S_IWUSR)
        perm[2] = 'w';
    if (st.st_mode & S_IXUSR)
        perm[3] = 'x';

    /* group's permission */
    if (st.st_mode & S_IRGRP)
        perm[4] = 'r';
    if (st.st_mode & S_IWGRP)
        perm[5] = 'w';
    if (st.st_mode & S_IXGRP)
        perm[6] = 'x';

    /* other group permission */
    if (st.st_mode & S_IROTH)
        perm[7] = 'r';
    if (st.st_mode & S_IWOTH)
        perm[8] = 'w';
    if (st.st_mode & S_IXOTH)
        perm[9] = 'x';
    // copy perm back to buf
    strncpy(buf, perm, sizeof(perm));
    return 0;
}

int get_file_link(const char *file_path, char *item, size_t size)
{
    struct stat st;
    // 根据文件路径取得文件状态
    if (stat(file_path, &st) < 0) { // error
        err_ret("get_file_link stat error on file %s", file_path);
        return -1;
    }
    int n = snprintf(item, size, " %lu", st.st_nlink);
    return n;
}

int get_file_owner(const char *file_path, char *item, size_t size)
{
    struct stat st;
    struct passwd pwd;
    struct passwd *res_pwd;
    long bufsize;
    int s;
    int err_flag = 0;

    // get file uid
    if (stat(file_path, &st) < 0) { // error
        err_ret("get_file_owner stat error on file %s", file_path);
        return -1;
    }

    // get buffer max size
    if ((bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1) {
        bufsize = GETPW_R_SIZE_MAX_GUESS; // guess
    }
    char *buf = malloc(bufsize);
    if (buf == NULL) {
        err_ret("malloc error");
        return -1;
    }

    // get password file entry to get owner's name by uid
    s = getpwuid_r(st.st_uid, &pwd, buf, bufsize, &res_pwd);
    if (res_pwd == NULL) {
        if (s == 0) {// Not found
            fprintf(stderr, "file owner not found\n");
        } else {
            errno = s;
            err_ret("getpwuid_r error");
        }
        err_flag = -1;
    }

    if (err_flag != 0) {
        free(buf);
        return -1;
    }
    int n = snprintf(item, size, " %s", pwd.pw_name); // 将文件所属uid对应用户名，添加到item末尾
    free(buf);
    return n;
}

int get_file_group(const char *file_path, char *item, size_t size)
{
    struct stat st;
    long bufsize;
    int s;
    int err_flag = 0;
    struct group grp, *res_grp;

    // get file gid
    if (stat(file_path, &st) < 0) { // error
        err_ret("get_file_group stat error on file %s", file_path);
        return -1;
    }
    // get buffer max size
    if ((bufsize = sysconf(_SC_GETGR_R_SIZE_MAX)) == -1) {
        bufsize = GETGR_R_SIZE_MAX_GUESS; // guess
    }
    char *buf = malloc(bufsize);
    if (buf == NULL) {
        err_ret("malloc error");
        return -1;
    }

    // get group's name of gid
    s = getgrgid_r(st.st_gid, &grp, buf, bufsize, &res_grp);
    if (res_grp == NULL) {
        if (s == NULL)
            fprintf(stderr, "File group not found\n");
        else {
            errno = s;
            err_ret("getgrgid_r error");
        }
        err_flag = -1;
    }

    if (err_flag != 0) {
        free(buf);
        return -1;
    }
    int n = snprintf(item, size, " %s", grp.gr_name);
    free(buf);
    return n;
}

int get_file_size(const char *file_path, char *item, size_t size)
{
    struct stat st;
    // get file size
    if (stat(file_path, &st) < 0) { // error
        err_ret("get_file_size stat error on file %s", file_path);
        return -1;
    }
    return snprintf(item, size, " %ld", st.st_size);
}

/**
 * get last modify time of file_path
 * @param file_path target file path
 * @param[out] item
 * @param size byte number of item
 * @return
 * @note 因为服务器与用户同处一个时区，不考虑时区转化问题，同属一个local timezone
 */
size_t get_file_mtime(const char *file_path, char *item, size_t size)
{
    struct stat st;
    // get file mtime
    if (stat(file_path, &st) < 0) { // error
        err_ret("get_file_mtime stat error on file %s", file_path);
        return -1;
    }

    struct tm res_time;
    time_t now_time = time(NULL);
    size_t n = 0;

    // convert file mtime to local time, storing in res_time
    if (localtime_r(&st.st_mtime, &res_time) == NULL) {
        err_ret("localtime_r error");
        return -1;
    }

    //  within half a year, do not print year, but print hour and minute
    if (now_time < st.st_mtime + (365 / 2 ) * 24 * 3600) {
        n = strftime(item, size, " %b %d %R", &res_time); // i.g. Jan 19 21:24
    }
    else {
        // over half a year, print year, but do not print hour and minute
        n = strftime(item, size, " %b %d %Y", &res_time); // i.g. Jan 19 2012
    }

    return n;
}

/**
 * 遍历指定物理路径的目录base_dir_path_phy, 并将结构存放到buf
 * @param base_dir
 * @param buf 存放结果字符串，相当于存放命令 "$ ls -al"结果
 * @param size buf实际大小
 * @return 成功返回0,并以/bin/ls 格式列出子文件项信息，存放到buf；失败返回-1
 * @note
 * /bin/ls格式：
 * 形如"$ ls -l"输出结果的格式
 * drwxr-xr-x  2 martin martin  4096 Jul 23 11:41 Music
 * -rw-r--r--  1 martin martin  32 Jun 20 11:47 test.txt
 * 文件权限(-rw-r--r--) 链接数 拥有者 组 文件大小 最后修改日期 文件名
 *
 * 参考: https://cr.yp.to/ftp/list/binls.html
 */

/**
 * list file of the directory base_dir
 * @param dir cwd info
 * @param base_dir
 * @param[out] buf to store the result of every file's stat info(/bin/ls format). a file's stat info , i.e. an item string,
 * end with "\r\n"
 * @param size byte number of buf, need to be large enough to store the result
 * @return
 * @details
 * /bin/ls format:
 * drwxr-xr-x  2 martin martin  4096 Jul 23 11:41 Music
 * -rw-r--r--  1 martin martin  32 Jun 20 11:47 test.txt
 * just like the result of shell command "$ ls -l"
 * @see https://cr.yp.to/ftp/list/binls.html
 */

int dir_listFiles(Dir *dir, struct ef_string *s)
{
    if (!dir || !dir->cwd_vir || !s)
        return -1;

    // get cwd physical path
    char phy_path[PATH_MAX]; /* to store cwd physical path */
    if (dir_getCWDPhysicalPath(dir, phy_path, sizeof(phy_path)) == NULL) {
        err_msg("get physical path of current working directory failed");
        return -1;
    }
    printf("cwd_phy = %s\n", phy_path);
    dir_print(dir);

    const char *base_dir = phy_path;
    DIR *dirp;
    int err_flg = 0;
    // open dir
    if ((dirp = opendir(base_dir)) == NULL) {
        err_ret("opendir error on %s", base_dir);
        return -1;
    }

    // get path max
    long path_max = pathconf(base_dir, _PC_PATH_MAX);
    if (path_max == -1) {
        path_max = PATH_MAX_GUESS;
    }
    // malloc space to store file path
    char *file_path = malloc(path_max);
    if (!file_path) {
        err_msg("malloc error");
        return -1;
    }

    // malloc direnct obejct entry to store a item info
    struct dirent *entryp, *res_entryp;
    long name_max = pathconf(base_dir, _PC_NAME_MAX);
    if (name_max == -1) {
        name_max = NAME_MAX_GUESS; // guess
    }
    int len = offsetof(struct dirent, d_name) + name_max + 1; // + 1 for NULL terminate byte
    entryp = malloc(len);

    int ret;
    struct stat st;

    // walk all of sub-files in the base_dir
    while (1) {
        char item[1024];
        size_t off = 0;
        if ((ret = readdir_r(dirp, entryp, &res_entryp)) > 0) { // error
            fprintf(stderr, "readdir_r error: %d\n", ret);
            err_flg = -1;
            break;
        }
        if (res_entryp == NULL) // directory stream end
            break;

        // ignore "." and ".."
        if (0 == strcmp(entryp->d_name, ".") || 0 == strcmp(entryp->d_name, ".."))
            continue;

        // get the file physical path = base_dir + d_name
        strcpy(file_path, base_dir); // file's parent directory
        if (!str_end_with(base_dir, '/')) {
            strcat(file_path, "/");
        }
        strcat(file_path, entryp->d_name); // file's name

        // check if the file is accessible
        if (stat(file_path, &st) < 0) { /* error */
            err_ret("dir_listFiles stat error on %s", file_path);
            err_flg = -1;
            break;
        }

        // 10bit permission
        get_permission_10bit(file_path, item, sizeof(item));
        off += strlen(item);

        // link counter
        ret = get_file_link(file_path, item + off, sizeof(item) - off);
        if (ret < 0) {
            err_ret("get_file_link error");
            break;
        }
        off += ret;

        // owner name
        ret = get_file_owner(file_path, item + off, sizeof(item) - off);
        if (ret < 0) {
            err_ret("get_file_link error");
            break;
        }
        off += ret;

        // group name
        ret = get_file_group(file_path, item + off, sizeof(item) - off);
        if (ret < 0) {
            err_ret("get_file_link error");
            break;
        }
        off += ret;

        // file size
        ret = get_file_size(file_path, item + off, sizeof(item) - off);
        if (ret < 0) {
            err_ret("get_file_size error");
            break;
        }
        off += ret;

        // last modify time
        ret = (int)get_file_mtime(file_path, item + off, sizeof(item) - off);
        if (ret < 0) {
            err_ret("get_file_link error");
            break;
        }
        off += ret;

        // file name
        off += snprintf(item + off, sizeof(item) - off, " %s", entryp->d_name);

        // add "\r\n" to the end of every item
        strcat(item, "\r\n");
        off += strlen("\r\n");

        ef_string_append(s, item);
    }

    // close dir
    closedir(dirp);
    // free malloc space
    free(file_path);
    free(entryp);
    // return result by error flag
    if (err_flg != 0) return -1;
    return 0;
}
